<!--
本封装好的组件，主要用于打开摄像头并截图
1、调用该组件的数据，要用Pinia数据状态管理，在Pinia中，要定义2个数组，2个获取该数组值的函数
   一个数组是转载图片，一个转载图片转换为File格式的文件
2、该组件在拍照时，可以显示截图后的图片，完成后点击提交按钮，会把拍照后获得的数据存到Pinia的store中
3、该组件设定的拍摄分辨率最高为1920*1080。同时组件显示的图像大小事300*200
4、该插件会传输2个数组到pinia中，分别是photoFileList，对应的是摄像头拍照数据；uploadFileList，对应
   的是文件上传组件的数据
-->

<script setup lang="ts">
import {ref, watch} from "vue";
import { useDataPublicStore } from '@/stores/dataPublicStore.ts';
import {ElMessage, ElMessageBox, UploadProps, UploadUserFile} from "element-plus";
import ContextMenu from "@imengyu/vue3-context-menu";

const dataPublicStore=useDataPublicStore();
//   这几个参数是打开摄像头并用于截图的参数
const mediaStreamTrack=ref({});
const video_stream=ref('');
const imgSrc=ref('');
const canvas=ref(null);
const context=ref(null);
const video=ref(null);
const cameraFlag=ref(false);
// 这个和分辨率有关
const width=ref(1920);
const height=ref(1080);
//  用于存放截图后图像的base64数据的数组
const PhotographList=ref([]);
//  用于存放截图后并把base64格式数据转换成File格式的数组--该数组主要用于FormData的append方法向后端传送数据用格式。
const photoFiles=ref([]);


// 专门修正element-ui中图片预览必须要从第一张开始的bug
function  getPreviewImgList (index:number) {
  let arr = [];
  for (let i = 0; i < PhotographList.value.length; i++) {
    arr.push(PhotographList.value[i + index]);
    if (i + index >= PhotographList.value.length - 1) { // 为了取后边小值（采用绝对值）
      index = 0 - (i + 1);
    }
  }
  return arr;
}

// 对应的右键菜单
const onContextMenu=(e:MouseEvent,index:any)=>{
  ContextMenu.showContextMenu({
    x:e.x,
    y:e.y,
    items:[
      {
        label:'删除',
        onClick(e,index) {
          PhotographList.value.splice(index,1);
          photoFiles.value.splice(index,1);
        },
      }
    ]
  })
}

//  这个方法，是点击显示在网页上的图片时，去掉photographList中对应的元素和去掉photoFiles中对应的元素。
// const removePhoto=(index:number)=>{
//   // 通过数组的splice方法，去掉对应的元素，第一个参数是下标值，第二个参数是从下标值开始，去掉多少个元素，这里是1，就是去掉当前下标值的元素
//   PhotographList.value.splice(index,1);
//   // 通过数组的splice方法，去掉对应的元素，第一个参数是下标值，第二个参数是从下标值开始，去掉多少个元素，这里是1，就是去掉当前下标值的元素
//   photoFiles.value.splice(index,1);
// }

//  这个函数是打开摄像头，并定义摄像头的一些参数，核心函数。
const getCamera=()=> {
  // canvas.value = document.querySelector('canvas');
  canvas.value=document.querySelector('#canvas');
  const constraints = { audio: false, video: { width: 1920, height: 1080, transform: 'scaleX(-1)' } };
  // 旧版本浏览器可能根本不支持mediaDevices，我们首先设置一个空对象
  if (navigator.mediaDevices === undefined) {
    navigator.mediaDevices = {};
  }
  // 正常支持版本
  navigator.mediaDevices.getUserMedia(
      //     {
      //   video: true,//默认前置
      //   //video: { facingMode: { exact: "environment" } },//后置
      // }
      constraints
  )
      .then((stream) => {
        // 摄像头开启成功
        mediaStreamTrack.value = typeof stream.stop === 'function' ? stream : stream.getTracks()[0];
        video_stream.value = stream;
        video.value.srcObject = stream;
        video.value.play();
      })
      .catch(err => {
        console.log(err);
      });
}

//   这个函数是截取摄像头的图片，形成base64格式数据，并把数据pop进PhotographList数组中
const takeImage=()=>{
  if (cameraFlag.value===true){
    // 设定拍照的分辨率
    canvas.value.setAttribute("width", width.value);
    canvas.value.setAttribute("height", height.value);
    // 关闭抗锯齿
    context.value = canvas.value.getContext('2d');
    console.log('拍照');
    // 点击canvas画图,width,height设定video分辨率
    context.value.drawImage(
        video.value,
        0,
        0,
        width.value,
        height.value,
    );

    // 获取图片base64链接
    imgSrc.value = canvas.value.toDataURL('image/jpeg');
    // 把获取到的图片（实际图片格式）存到一个数组当中
    PhotographList.value.push(imgSrc.value);

    // ***********************************这里有个问题，逻辑是有个问题****************************************************************
    // 把图片格式数组转换为File格式数组
    photoFiles.value=base64ToFile(PhotographList.value,String((new Date()).valueOf()));

    /*************************************************************************************************************************************/
    // console.log(photoFiles.value);

//   把已经转换成File格式的文件对象存储到Store中，被其它组件调用
    dataPublicStore.setPhotoFiles(photoFiles.value);
    //   把截图的photographList数组存到Store中，被其它组件调用
    dataPublicStore.saveDataList(PhotographList.value);
  }else {
    ElMessage({
      type:'warning',
      message:'请打开摄像头！'
    })
  }
}

// 调用getCamera函数，打开摄像头，初始化相关数据
const OpenCamera=()=>{
  // 摄像头开关标志
  cameraFlag.value=true;
  // console.log('打开摄像头');
  getCamera();
}

// 关闭摄像头，同时清空PhotographList数组和photoFiles数组数据
const CloseCamera=()=>{
  console.log("关闭摄像头");
  video.value.srcObject.getVideoTracks()[0].stop();
  // 图片格式数组清空
  PhotographList.value=[];
  // 文件格式数组清空
  photoFiles.value=[];
  // 判断摄像头是否打开标志值为false，表示摄像头已经关闭
  cameraFlag.value=false;
}

//  通用方法，把base64格式的数据批量转换成File格式数据，用于向后端通过MultipartFile方式传送
//  总要的一点***********MultipartFile方式得append格式，要求不是用append整个数组，而是append每个数组元素。
//  append("name",data)：两个参数，name要与后端MultipartFile[] name 中的name的名字一模一样。不得改变，即使是append执行多次，data可以不同，但这个name必须一样
const base64ToFile=(base64List:[],fileName:string)=>{
  //  定义一个数组，用于转载转换后的File格式数据
  let photoFileList=[];
  //  通过数组的forEach方法，遍历传进来的参数base64List数组
  base64List.forEach((base64)=>{
    // 将base64按照 , 进行分割 将前缀  与后续内容分隔开
    const data = base64.split(',')
    // 利用正则表达式 从前缀中获取图片的类型信息（image/png、image/jpeg、image/webp等）
    const type = data[0].match(/:(.*?);/)[1]
    // 从图片的类型信息中 获取具体的文件格式后缀（png、jpeg、webp）
    const suffix = type.split('/')[1]
    // 使用atob()对base64数据进行解码  结果是一个文件数据流 以字符串的格式输出
    const bstr = window.atob(data[1])
    // 获取解码结果字符串的长度
    let n = bstr.length
    // 根据解码结果字符串的长度创建一个等长的整形数字数组
    // 但在创建时 所有元素初始值都为 0
    const u8arr = new Uint8Array(n)
    // 将整形数组的每个元素填充为解码结果字符串对应位置字符的UTF-16 编码单元
    while (n--) {
      // charCodeAt()：获取给定索引处字符对应的 UTF-16 代码单元
      u8arr[n] = bstr.charCodeAt(n)
    }
    // 利用构造函数创建File文件对象
    // new File(bits, name, options)
    // 将File文件对象返回给方法的调用者
    // 利用构造函数创建File文件对象
    // new File(bits, name, options)
    // 将File文件对象返回给方法的调用者
    // photoFileList把转换好格式的数据保存好
    photoFileList.push(new File([u8arr], `${fileName}.${suffix}`, {
      type: type
    }))
  })
  // 返回一个已经装好转好成File格式数据的数组
  return photoFileList;
}

/*========================文件上传模块===============================*/
const finaFileList=ref<UploadUserFile[]>([])
// 定义上传文件列表，ref方式
const fileList = ref<UploadUserFile[]>([])
// 下面三个箭头函数不用管，返回的是一个UploadProps[]的值，以后用到再添加

const handleRemove: UploadProps['onRemove'] = (file, uploadFiles) => {
  finaFileList.value=[];
  uploadFiles.forEach((item)=>{
    finaFileList.value.push(item.raw)
  })
  dataPublicStore.setUploadFileList(finaFileList.value);
}

const handlePreview: UploadProps['onPreview'] = (uploadFile) => {
  // console.log(uploadFile)
}
const handleExceed: UploadProps['onExceed'] = (files, uploadFiles) => {
  ElMessage.warning(
      `一次总共可以上传50个文件, 你这次已经选择了 ${files.length} 个文件, add up to ${
          files.length + uploadFiles.length
      } totally`
  )
}


// 文件上传模块的typeScript代码，关键是这个段代码

const handelChange:UploadProps['onChange']=(file)=>{
  // console.log(file.size/(1024*1024));
  if ((file.size)/(1024*1024)<20)
  {
    /****************************************************************************/
    finaFileList.value.push(file.raw)
    // 千万，千万，千万要注意，append中的name,必须，必须，必须与后端的方法参数的MultipartFile[] files的
    // files名字一抹一样，如后端的是MultipartFile[] files，那么这个就是"files"
    // formData.append("eqPhotoList",file.raw);
  }
  else {
    alert("文件大于20M,无法上传！");
    return;
  }
  dataPublicStore.setUploadFileList(finaFileList.value);
}

//
const beforeRemove: UploadProps['beforeRemove'] = (uploadFile, uploadFiles) => {
  return ElMessageBox.confirm(
      `取消上传 ${uploadFile.name} ?`
  ).then(
      () => true,
      () => false
  )
}


// 监听摄像头数据和文件上传数据变化，自动变更dataStore数据
// photoFiles摄像头拍照的file数据，finalFileList是文件上传组件de File数据

watch(photoFiles,(newVal,oldVal)=>{
  dataPublicStore.setPhotoFiles(newVal);
  console.log(dataPublicStore.photoFileList);
  console.log(oldVal);
},{
  deep:true
})
watch(finaFileList,(newVal,oldVal)=>{
  dataPublicStore.setUploadFileList(newVal);
  console.log(dataPublicStore.uploadFileList);
},{
  deep:true
})









</script>

<template>
  <br>
  <h3 style="margin-left: 120px">摄像头拍照</h3>
  <div class="mainContain">

    <el-button  type="success" size="small" @click="OpenCamera">打开摄像头</el-button>
    <el-button type="success" size="small" @click="takeImage">拍照</el-button>
    <el-button type="success" size="small" @click="CloseCamera">关闭摄像头</el-button>
    <br><br>
    <div class="publish">
      <!--    定义一个摄像头显示的框，这个框实时显示摄像头的摄像内容-->
      <!--    注意这个ref，是vue的语法，引用了video的内容对象给TypeScript使用，在TypeScript要新建一个video的ref对象，值为null-->
      <video ref="video" id="video"></video>
      <!--    新建一个canvas，用于转载拍照的图像，这里是不显示，仅仅用于获取DOM的对象数据-->
      <canvas id="canvas" style="display: none"  ref="canvas"></canvas>
      <!--style中，图片显示的大小尺寸 -->
      <div class="block">
        <el-image v-for="(ele,index) in PhotographList" :key="index"
                  style="width: 150px; height: 100px"
                  :src="ele"
                  :zoom-rate="1.2"
                  :max-scale="5"
                  :min-scale="0.5"
                  :preview-src-list="getPreviewImgList(index)"
                  fit="cover"
                  @contextmenu.prevent
                  @contextmenu.stop="onContextMenu($event,index)"
        >
        </el-image>
      </div>
    </div>
    <br>



  </div>

  <!--  文件上传模块-->
  <div class="upload">
    <h3 style="margin-left: 50px">已有文件上传</h3>
    <el-upload
        class="upload-demo"
        ref="upload"
        action=""
        v-model:file-list="fileList"
        :on-preview="handlePreview"
        :on-remove="handleRemove"
        :before-remove="beforeRemove"
        :auto-upload="false"
        :multiple="true"
        :on-change="handelChange"
        name="cover"
        :limit="20"
        :on-exceed="handleExceed"
        :drag="true"
        accept=".pdf,.jpg,.png,.bmp,.gif,.jpeg"
    >
      <el-button style="width: 150px" type="primary">请选择需要上传的文件</el-button>
      <template #tip>
        <div class="el-upload__tip">
          单个图片文件不要超过3M.
        </div>
        <!--        <el-button @click="filesRemove">清空文件列表</el-button>-->
      </template>
    </el-upload>
  </div>


</template>

<style scoped>
.mainContain {
  padding: 10px;
  margin: 10px;
  display: flex;
}
/*摄像头video框大小*/
video {
  width: 240px;
  height: 160px;
  margin: 0;
}
canvas {
  width: 240px;
  height: 240px;
  margin: 0;
}
button {
  width: 100px;
  height: 30px;
  position: relative;
  bottom: 0;
  left: 0;
  background-color: rgb(22, 204, 195);
}


.upload {
  width: 180px;
  height: 30px;
  margin: 10px;


}

</style>